package org.sibframework.sda.manager;

import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.sibframework.sda.SdaFactory;
import org.sibframework.sda.SimpleDataAccess;
import org.sibframework.sda.ids.IdGen;
import org.sibframework.sda.ids.IdGenFactory;
import org.sibframework.utils.Describer;
import org.sibframework.utils.TraceTool;

public class ModelManager<E> implements IModelManager<E> {
	
	private long cachedId = 0;
	
	public static boolean useUUID = false;
		
	private SimpleDataAccess sda = null;
	Class<E> modelClass = null;

	private String ID_NAME = "id";
	
	private IdGen<E> idGen = null;

	
	public ModelManager(Class<E> clas) throws SQLException {
		super();
		this.modelClass = clas;
		this.idGen = IdGenFactory.getIdGen(clas);
	}
	
	
	
	public Class<E> getModelClass() {
		return modelClass;
	}
	
	
	

	@Override
	public SimpleDataAccess getSDA() {
		
		if(sda != null && !sda.isClosed()) {
			return sda;
		} else if (sda != null && sda.isClosed()) {
			System.out.println("★ Sda Warning: Use a Closed SDA #" + this.hashCode() + ". ");;
			Describer.describe(new TraceTool().trace(), new PrintWriter(System.err), "");
			System.out.println("---#" + this.hashCode() + " was closed by ");
			Describer.describe(sda.getClosedBy());
		}
		
		try {
			return SdaFactory.getInstance();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return null;
	}



	@Override
	public void setSDA(SimpleDataAccess sda) {
		this.sda = sda;
	}



	@Override
	public List<E> list() {
		return getSDA().listAll(modelClass);
	}

	@Override
	public List<E> search(E example) {
		return getSDA().search(example);
	}

	@Override
	public E searchFirst(E example) {
		return getSDA().searchFirst(example);
	}
	
	public List<E> search(E example, E like) {
		return getSDA().search(example, like);
	}
	
	@Override
	public List<E> query(String sql) {
		return getSDA().query(sql, getModelClass());
	}
	
	@Override
	public List<E> query(String sql, Object[] params) {
		return getSDA().query(sql, params, getModelClass());
	}
	
	@Override
	public E getById(Serializable id) {
		return (E) getSDA().findByPK(getModelClass(), id);
	}

	@Override
	public E getById(E object) {
		return (E) getSDA().findByPK(object);
	}

	@Override
	public E getByCode(String codeName, Serializable code) {
		return (E) getSDA().getByCode(this.modelClass, codeName, code);
	}
	
	@Override
	public List<E> searchByCode(String codeName, Serializable code) {
		return  getSDA().searchByCode(this.modelClass, codeName, code);
	}
	
	
	@Override
	public int update(E object) throws SQLException {
		return getSDA().update2(object);
	}


	@Override
	public int update(E[] objects) throws SQLException {
		return getSDA().update2(objects);
	}

	@Override
	public int updateFields(Map<String, Object> values) throws SQLException {
		return getSDA().update2(this.modelClass, (Serializable) values.get(ID_NAME), values);
	}
	
	@Override
	public int insert(E object) throws SQLException {
		if (this.getId(object) == null) {
			this.setId(object, this.idGen.next());
		}
		return getSDA().insert2(object);
	}



	@Override
	public int insert(E[] objects) throws SQLException {
		for (E e : objects) {
			if (this.getId(e) == null) {
				this.setId(e, this.idGen.next());
			}
		}
		return getSDA().insert2(objects);
	}
	
	
	@Override
	public int remove(E object) throws SQLException {
		return getSDA().delete(object);
	}
	

	@Override
	public int remove(E[] objects) throws SQLException {
		return getSDA().delete(objects);
	}

	
	
	@Override
	public int remove(Serializable id) throws SQLException {
		return getSDA().deleteByPK(this.getModelClass(), id);
	}
	@Override
	public String getModelName() {
		return modelClass.getSimpleName();
	}

	@Override
	public synchronized E createModel() {
		try {
			E e = modelClass.newInstance();
			if (useUUID) {
				this.setId(e, this.genUUID());
			} else {
				long pk = getNextPk();
				this.setId(e, pk);
			}
			return e;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public Object getId(E e) {
		try {
			return PropertyUtils.getProperty(e, ID_NAME);
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		} catch (InvocationTargetException e1) {
			e1.printStackTrace();
		} catch (NoSuchMethodException e1) {
			e1.printStackTrace();
		}
		return null;
	}
	
	public void setId(E e, Serializable id) {
		try {
			BeanUtils.setProperty(e, ID_NAME, id.toString());
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		} catch (InvocationTargetException e1) {
			e1.printStackTrace();
		}
	}
	
	public String genUUID() {
		return UUID.randomUUID().toString().replace("-", "");
	}

	public synchronized long getNextPk() {
		try {
			return this.idGen.next();
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}




	@Override
	public void close() {
		if (this.sda != null) {
			this.sda.close();
		}
	}




	
	
	
}
